﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Net;
using System.Net.Sockets;
using System.Threading;

using LongNet.Common;
using LongNet.C2S;
using LongNet.S2C;
using LongNet.P2P;
using LongNet.P2PServer;
using LongNet.P2PClient;

namespace LongNet.Common
{
    /// <summary>
    /// P2P服务器
    /// </summary>
     public class P2PServer
    {
    }

     /// <summary>

     /// P2PConsts 的摘要说明。

     /// </summary>

     public class P2PConsts
     {

         /// <summary>

         /// 服务器侦听端口号

         /// </summary>

         public const int SRV_PORT = 2280;

     }

     /// <summary>

     /// User 的摘要说明。

     /// </summary>

     [Serializable]

     public class User
     {

         protected string userName;

         protected IPEndPoint netPoint;



         public User(string UserName, IPEndPoint NetPoint)
         {

             this.userName = UserName;

             this.netPoint = NetPoint;

         }

         public string UserName
         {

             get { return userName; }

         }



         public IPEndPoint NetPoint
         {

             get { return netPoint; }

             set { netPoint = value; }

         }

     }

     /// <summary>

     /// UserCollection 的摘要说明。

     /// </summary>

     [Serializable]

     public class UserCollection : CollectionBase
     {

         public void Add(User user)
         {
             InnerList.Add(user);
         }



         public void Remove(User user)
         {

             InnerList.Remove(user);

         }



         public User this[int index]
         {

             get { return (User)InnerList[index]; }

         }



         public User Find(string userName)
         {

             foreach (User user in this)
             {

                 if (string.Compare(userName, user.UserName, true) == 0)
                 {

                     return user;

                 }

             }

             return null;

         }

     }

     /// <summary>

     /// FormatterHelper 序列化，反序列化消息的帮助类

     /// </summary>

     public class FormatterHelper
     {

         public static byte[] Serialize(object obj)
         {

             BinaryFormatter binaryF = new BinaryFormatter();

             MemoryStream ms = new MemoryStream(1024 * 10);

             binaryF.Serialize(ms, obj);

             ms.Seek(0, SeekOrigin.Begin);

             byte[] buffer = new byte[(int)ms.Length];

             ms.Read(buffer, 0, buffer.Length);

             ms.Close();

             return buffer;

         }



         public static object Deserialize(byte[] buffer)
         {

             BinaryFormatter binaryF = new BinaryFormatter();

             MemoryStream ms = new MemoryStream(buffer, 0, buffer.Length, false);

             object obj = binaryF.Deserialize(ms);

             ms.Close();

             return obj;

         }

     }

     /// <summary>

     /// Message base class

     /// </summary>

     [System.Serializable]
     public abstract class MessageBase
     {

     }
}
namespace LongNet.C2S
{
    #region  客户端消息
    /// <summary>

    /// 客户端发送到服务器的消息基类

    /// </summary>
    [System.Serializable]
    public abstract class CSMessage : MessageBase
    {

        private string userName;

        protected CSMessage(string anUserName)
        {

            userName = anUserName;

        }

        public string UserName
        {

            get { return userName; }

        }

    }

    /// <summary>

    /// 用户登录消息

    /// </summary>
     [System.Serializable]
    public class LoginMessage : CSMessage
    {

        private string password;

        public LoginMessage(string userName, string password)
            : base(userName)
        {

            this.password = password;

        }

        public string Password
        {

            get { return password; }

        }

    }

    /// <summary>

    /// 用户登出消息

    /// </summary>
     [System.Serializable]
    public class LogoutMessage : CSMessage
    {

        public LogoutMessage(string userName)
            : base(userName)

        { }

    }

    /// <summary>

    /// 请求用户列表消息

    /// </summary>
     [System.Serializable]
    public class GetUsersMessage : CSMessage
    {

        public GetUsersMessage(string userName)
            : base(userName)

        { }

    }

    /// <summary>

    /// 请求Purch Hole消息

    /// </summary>
     [System.Serializable]
    public class TranslateMessage : CSMessage
    {

        protected string toUserName;

        public TranslateMessage(string userName, string toUserName)
            : base(userName)
        {

            this.toUserName = toUserName;

        }

        public string ToUserName
        {

            get { return this.toUserName; }

        }

    }


    #endregion  客户端消息
}

// Message from server to the client
namespace LongNet.S2C
{

    /// <summary>

    /// 服务器发送到客户端消息基类

    /// </summary>
     [System.Serializable]
    public abstract class SCMessage : MessageBase

    { }

    /// <summary>

    /// 请求用户列表应答消息

    /// </summary>
     [System.Serializable]
    public class GetUsersResponseMessage : SCMessage
    {

        private UserCollection userList;

        public GetUsersResponseMessage(UserCollection users)
        {

            this.userList = users;

        }

        public UserCollection UserList
        {

            get { return userList; }

        }

    }

    /// <summary>

    /// 转发请求Purch Hole消息

    /// </summary>
     [System.Serializable]
    public class SomeOneCallYouMessage : SCMessage
    {

        protected System.Net.IPEndPoint remotePoint;

        public SomeOneCallYouMessage(System.Net.IPEndPoint point)
        {

            this.remotePoint = point;

        }

        public System.Net.IPEndPoint RemotePoint
        {

            get { return remotePoint; }

        }

    }

}

   // Message from peer to the peer
  namespace LongNet.P2P

    {

        /// <summary>

        /// 点对点消息基类

        /// </summary>
       [System.Serializable]
        public abstract class PPMessage : MessageBase

        {}

        /// <summary>

        /// 测试消息

        /// </summary>
       [System.Serializable]
        public class WorkMessage : PPMessage

        {

            private string message;

            public WorkMessage(string msg)

            {

                message = msg;

            }

            public string Message

            {

                get { return message; }

            }

        }

        /// <summary>

        /// 测试应答消息

        /// </summary>
       [System.Serializable]
        public class ACKMessage : PPMessage

        {

        }

        /// <summary>

        /// P2P Purch Hole Message

        /// </summary>
       [System.Serializable]
        public class TrashMessage : PPMessage

        {}

    }

  namespace LongNet.P2PServer
  {     

      /// <summary>

      /// AppClass 的摘要说明。

      /// </summary>

      public class AppClass
      {

          public static void StartServer()
          {

              Server server = new Server();

              try
              {

                  server.Start();

                  Console.ReadLine();

                  server.Stop();

              }

              catch
              {

              }

          }

      }

      /// <summary>

      /// Server 的摘要说明。

      /// </summary>

      public class Server
      {

          private UdpClient server;

          private UserCollection userList;

          private Thread serverThread;

          private IPEndPoint remotePoint;



          public Server()
          {

              userList = new UserCollection();

              remotePoint = new IPEndPoint(IPAddress.Any, 0);

              serverThread = new Thread(new ThreadStart(Run));

          }



          public void Start()
          {

              try
              {

                  server = new UdpClient(P2PConsts.SRV_PORT);

                  serverThread.Start();

                  Console.WriteLine("P2P Server started, waiting client connect...");

              }

              catch (Exception exp)
              {

                  Console.WriteLine("Start P2P Server error: " + exp.Message);

                  throw exp;

              }

          }



          public void Stop()
          {

              Console.WriteLine("P2P Server stopping...");

              try
              {

                  serverThread.Abort();

                  server.Close();

                  Console.WriteLine("Stop OK.");

              }

              catch (Exception exp)
              {

                  Console.WriteLine("Stop error: " + exp.Message);

                  throw exp;

              }



          }



          private void Run()
          {

              byte[] buffer = null;

              while (true)
              {

                  byte[] msgBuffer = server.Receive(ref remotePoint);

                  try
                  {

                      object msgObj = FormatterHelper.Deserialize(msgBuffer);

                      Type msgType = msgObj.GetType();

                      if (msgType == typeof(C2S.LoginMessage))
                      {

                          // 转换接受的消息

                          LoginMessage lginMsg = (LoginMessage)msgObj;

                          Console.WriteLine("has an user login: {0}", lginMsg.UserName);

                          // 添加用户到列表

                          IPEndPoint userEndPoint = new IPEndPoint(remotePoint.Address, remotePoint.Port);

                          User user = new User(lginMsg.UserName, userEndPoint);

                          userList.Add(user);

                          // 发送应答消息

                          GetUsersResponseMessage usersMsg = new GetUsersResponseMessage(userList);

                          buffer = FormatterHelper.Serialize(usersMsg);

                          server.Send(buffer, buffer.Length, remotePoint);

                      }

                      else if (msgType == typeof(LogoutMessage))
                      {

                          // 转换接受的消息

                          LogoutMessage lgoutMsg = (LogoutMessage)msgObj;

                          Console.WriteLine("has an user logout: {0}", lgoutMsg.UserName);

                          // 从列表中删除用户

                          User lgoutUser = userList.Find(lgoutMsg.UserName);

                          if (lgoutUser != null)
                          {

                              userList.Remove(lgoutUser);

                          }

                      }

                      else if (msgType == typeof(TranslateMessage))
                      {

                          // 转换接受的消息

                          TranslateMessage transMsg = (TranslateMessage)msgObj;

                          Console.WriteLine("{0}(1) wants to p2p {2}", remotePoint.Address.ToString(), transMsg.UserName, transMsg.ToUserName);

                          // 获取目标用户

                          User toUser = userList.Find(transMsg.ToUserName);

                          // 转发Purch Hole请求消息

                          if (toUser == null)
                          {

                              Console.WriteLine("Remote host {0} cannot be found at index server", transMsg.ToUserName);

                          }

                          else
                          {

                              SomeOneCallYouMessage transMsg2 = new SomeOneCallYouMessage(remotePoint);

                              buffer = FormatterHelper.Serialize(transMsg);

                              server.Send(buffer, buffer.Length, toUser.NetPoint);

                          }

                      }

                      else if (msgType == typeof(GetUsersMessage))
                      {

                          // 发送当前用户信息到所有登录客户

                          GetUsersResponseMessage srvResMsg = new GetUsersResponseMessage(userList);

                          buffer = FormatterHelper.Serialize(srvResMsg);

                          foreach (User user in userList)
                          {

                              server.Send(buffer, buffer.Length, user.NetPoint);

                          }

                      }

                      Thread.Sleep(500);

                  }

                  catch { }

              }

          }

      }

  }

  namespace LongNet.P2PClient
  {
      /// <summary>

      /// AppClass 的摘要说明。

      /// </summary>

      public class AppClass
      {

          public static void StartClient()
          {

              Client client = new Client("183.16.136.184");
              Console.Write("请输入用户名:");
              string input = Console.ReadLine();
              client.ConnectToServer(input, "mypassword");

              client.Start();

              Console.WriteLine("test arguments");

              while (true)
              {

                  string str = Console.ReadLine();

                  client.PaserCommand(str);

              }

          }

      }

      /// <summary>

      /// Client 的摘要说明。

      /// </summary>

      public class Client : IDisposable
      {

          private const int MAXRETRY = 10;

          private UdpClient client;

          private IPEndPoint hostPoint;

          private IPEndPoint remotePoint;

          private UserCollection userList;

          private string myName;

          private bool ReceivedACK;

          private Thread listenThread;



          public Client(string serverIP)
          {

              ReceivedACK = false;

              remotePoint = new IPEndPoint(IPAddress.Any, 0);

              hostPoint = new IPEndPoint(IPAddress.Parse(serverIP), P2PConsts.SRV_PORT);

              client = new UdpClient();

              userList = new UserCollection();

              listenThread = new Thread(new ThreadStart(Run));

          }



          public void Start()
          {

              if (this.listenThread.ThreadState == ThreadState.Unstarted)
              {

                  this.listenThread.Start();

                  Console.WriteLine("You can input you command:\n");

                  Console.WriteLine("Command Type:\"send\",\"exit\",\"getu\"");

                  Console.WriteLine("Example : send Username Message");

                  Console.WriteLine("          exit");

                  Console.WriteLine("          getu");

              }

          }



          public void ConnectToServer(string userName, string password)
          {

              myName = userName;

              // 发送登录消息到服务器

              LoginMessage lginMsg = new LoginMessage(userName, password);

              byte[] buffer = FormatterHelper.Serialize(lginMsg);

              client.Send(buffer, buffer.Length, hostPoint);

              // 接受服务器的登录应答消息

              buffer = client.Receive(ref remotePoint);

              GetUsersResponseMessage srvResMsg = (GetUsersResponseMessage)FormatterHelper.Deserialize(buffer);

              // 更新用户列表

              userList.Clear();

              foreach (User user in srvResMsg.UserList)
              {

                  userList.Add(user);

              }

              this.DisplayUsers(userList);

          }



          /// <summary>

          /// 这是主要的函数：发送一个消息给某个用户(C)

          /// 流程：直接向某个用户的外网IP发送消息，如果此前没有联系过

          /// 那么此消息将无法发送，发送端等待超时。

          /// 超时后，发送端将发送一个请求信息到服务端，要求服务端发送

          /// 给客户C一个请求，请求C给本机发送打洞消息

          /// *以上流程将重复MAXRETRY次

          /// </summary>

          /// <param name="toUserName">对方用户名</param>

          /// <param name="message">待发送的消息</param>

          /// <returns></returns>

          private bool SendMessageTo(string toUserName, string message)
          {

              User toUser = userList.Find(toUserName);

              if (toUser == null)
              {

                  return false;

              }

              for (int i = 0; i < MAXRETRY; i++)
              {

                  WorkMessage workMsg = new WorkMessage(message);

                  byte[] buffer = FormatterHelper.Serialize(workMsg);

                  client.Send(buffer, buffer.Length, toUser.NetPoint);



                  // 等待接收线程将标记修改

                  for (int j = 0; j < 10; j++)
                  {

                      if (this.ReceivedACK)
                      {

                          this.ReceivedACK = false;

                          return true;

                      }

                      else
                      {

                          Thread.Sleep(300);

                      }

                  }

                  // 没有接收到目标主机的回应，认为目标主机的端口映射没有

                  // 打开，那么发送请求信息给服务器，要服务器告诉目标主机

                  // 打开映射端口（UDP打洞）

                  TranslateMessage transMsg = new TranslateMessage(myName, toUserName);

                  buffer = FormatterHelper.Serialize(transMsg);

                  client.Send(buffer, buffer.Length, hostPoint);

                  // 等待对方先发送信息

                  Thread.Sleep(100);

              }

              return false;

          }



          public void PaserCommand(string cmdstring)
          {

              cmdstring = cmdstring.Trim();

              string[] args = cmdstring.Split(new char[] { ' ' });

              if (args.Length > 0)
              {

                  if (string.Compare(args[0], "exit", true) == 0)
                  {

                      LogoutMessage lgoutMsg = new LogoutMessage(myName);

                      byte[] buffer = FormatterHelper.Serialize(lgoutMsg);

                      client.Send(buffer, buffer.Length, hostPoint);

                      // do clear something here

                      Dispose();

                      System.Environment.Exit(0);

                  }

                  else if (string.Compare(args[0], "send", true) == 0)
                  {

                      if (args.Length > 2)
                      {

                          string toUserName = args[1];

                          string message = "";

                          for (int i = 2; i < args.Length; i++)
                          {

                              if (args[i] == "") message += " ";

                              else message += args[i];

                          }

                          if (this.SendMessageTo(toUserName, message))
                          {

                              Console.WriteLine("Send OK!");

                          }

                          else

                              Console.WriteLine("Send Failed!");

                      }

                  }

                  else if (string.Compare(args[0], "getu", true) == 0)
                  {

                      GetUsersMessage getUserMsg = new GetUsersMessage(myName);

                      byte[] buffer = FormatterHelper.Serialize(getUserMsg);

                      client.Send(buffer, buffer.Length, hostPoint);

                  }

                  else
                  {

                      Console.WriteLine("Unknown command {0}", cmdstring);

                  }

              }

          }



          private void DisplayUsers(UserCollection users)
          {

              foreach (User user in users)
              {

                  Console.WriteLine("Username: {0}, IP:{1}, Port:{2}", user.UserName, user.NetPoint.Address.ToString(), user.NetPoint.Port);

              }

          }



          private void Run()
          {

              byte[] buffer;

              while (true)
              {

                  buffer = client.Receive(ref remotePoint);

                  object msgObj = FormatterHelper.Deserialize(buffer);

                  Type msgType = msgObj.GetType();

                  if (msgType == typeof(GetUsersResponseMessage))
                  {

                      // 转换消息

                      GetUsersResponseMessage usersMsg = (GetUsersResponseMessage)msgObj;

                      // 更新用户列表

                      userList.Clear();

                      foreach (User user in usersMsg.UserList)
                      {

                          userList.Add(user);

                      }

                      this.DisplayUsers(userList);

                  }

                  else if (msgType == typeof(SomeOneCallYouMessage))
                  {

                      // 转换消息

                      SomeOneCallYouMessage purchReqMsg = (SomeOneCallYouMessage)msgObj;

                      // 发送打洞消息到远程主机

                      TrashMessage trashMsg = new TrashMessage();

                      buffer = FormatterHelper.Serialize(trashMsg);

                      client.Send(buffer, buffer.Length, purchReqMsg.RemotePoint);

                  }

                  else if (msgType == typeof(WorkMessage))
                  {

                      // 转换消息

                      WorkMessage workMsg = (WorkMessage)msgObj;

                      Console.WriteLine("Receive a message: {0}", workMsg.Message);

                      // 发送应答消息

                      ACKMessage ackMsg = new ACKMessage();

                      buffer = FormatterHelper.Serialize(ackMsg);

                      client.Send(buffer, buffer.Length, remotePoint);

                  }

                  else if (msgType == typeof(ACKMessage))
                  {

                      this.ReceivedACK = true;

                  }

                  else if (msgType == typeof(TrashMessage))
                  {

                      Console.WriteLine("Recieve a trash message");

                  }

                  Thread.Sleep(100);

              }

          }

          #region IDisposable 成员



          public void Dispose()
          {

              try
              {

                  this.listenThread.Abort();

                  this.client.Close();

              }

              catch

              { }

          }



          #endregion

      }

  }








